/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.bindingtree;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import edu.ucsd.db.bassarisk.query.binding.Bindings.AggregatedValueBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.ComputedValueBinding;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.BindingTreeVisitor;
import edu.ucsd.db.bassarisk.query.predicate.PositionPredicates.PositionPredicate;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public abstract class BindingTreeElements {
  public static class BindingTreeEdge extends BindingTreeElement {
    public static List<BindingTreeEdge> list(final BindingTreeEdge... edges) {
      List<BindingTreeEdge> list = new ArrayList<BindingTreeEdge>();
      for (BindingTreeEdge edge : edges) {
        list.add(edge);
      }
      return list;
    }

    private final Collection<ComputedValueBinding> computations;
    private final Collection<PositionPredicate> predicates;
    private final EnumSet<EdgeFunction> functions;
    private final BindingTreeNode tailNode;

    public BindingTreeEdge(final BindingTreeNode child) {
      this.functions = EnumSet.noneOf(EdgeFunction.class);
      this.tailNode = child;
      this.computations = new ArrayList<ComputedValueBinding>();
      this.predicates = new ArrayList<PositionPredicate>();
    }

    public BindingTreeEdge(final BindingTreeNode child,
                           final AggregatedValueBinding aggregateResult) {
      this(child);
      this.computations.add(aggregateResult);
    }

    @Override
    public <R, A> R accept(final BindingTreeVisitor<R, A> visitor, final A arg) {
      return visitor.visitBindingTreeEdge(this, arg);
    }

    public EnumSet<EdgeFunction> functions() {
      return this.functions;
    }

    public BindingTreeNode tailNode() {
      return this.tailNode;
    }
    
    public Collection<ComputedValueBinding> computations() {
      return this.computations;
    }

    public Collection<PositionPredicate> predicates() {
      return this.predicates;
    }
  }

  public static abstract class BindingTreeElement {
    public abstract <R, A> R accept(BindingTreeVisitor<R, A> visitor, A arg);
  }

  public abstract static class BindingTreeNode extends BindingTreeElement {}

  public static class ChoiceNode extends BindingTreeNode {
    public abstract static class Choice {
      private final BitSet guard;

      public Choice() {
        this.guard = new BitSet();
      }

      public BitSet getGuard() {
        return this.guard;
      }
    }

    public static class ChooseAll extends Choice {
      private final List<BindingTreeEdge> children;

      public ChooseAll(final Collection<BindingTreeEdge> children) {
        this.children = new ArrayList<BindingTreeEdge>();
        this.children.addAll(children);
      }

      public Iterable<BindingTreeEdge> getChildren() {
        return this.children;
      }
    }

    public static class ChooseOne extends Choice {
      private final BindingTreeEdge child;

      public ChooseOne(final BindingTreeEdge child) {
        this.child = child;
      }

      public BindingTreeEdge getChild() {
        return this.child;
      }
    }

    private final List<Choice> choices;

    public ChoiceNode(final Collection<Choice> choices) {
      this.choices = new ArrayList<Choice>();
      this.choices.addAll(choices);
    }

    @Override
    public <R, A> R accept(final BindingTreeVisitor<R, A> visitor, final A arg) {
      return visitor.visitChoiceNode(this, arg);
    }

    public Iterable<Choice> getChoices() {
      return this.choices;
    }
  }

  public static class EstablishingNode extends BindingTreeNode {
    private final List<BindingTreeEdge> children;
    private final Binding establishedBinding; // FIXME should be final

    public EstablishingNode(final Binding binding) {
      this.establishedBinding = binding;
      this.children = new ArrayList<BindingTreeEdge>();
    }

    public EstablishingNode(final Binding binding,
                            final Collection<BindingTreeEdge> children) {
      this(binding);
      this.children.addAll(children);
    }

    @Override
    public <R, A> R accept(final BindingTreeVisitor<R, A> visitor, final A arg) {
      return visitor.visitEstablishingNode(this, arg);
    }

    public void addChild(final BindingTreeEdge bindingTreeEdge) {
      this.children.add(bindingTreeEdge);
    }

    public Iterable<BindingTreeEdge> children() {
      return this.children;
    }

    public Binding getEstablishedBinding() {
      return this.establishedBinding;
    }

    public int outDegree() {
      return this.children.size();
    }
  }
}
